{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Quantum Software Development Journey: \n",
    "# From Theory to Application with Classiq - Part 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Welcome to the Classiq Workshop Series for QClass 2024!**\n",
    "\n",
    "In this series, we will develop the skills needed to participate in quantum software development!\n",
    "\n",
    "- Week 1: Classiq's Basics & High-Level Functional Design\n",
    "- Week 2: Using Git as a Tool for In-Team Collaboration and Open Source Contributions\n",
    "- Weeks 3-4: Advanced Algorithms, Introduction to Quantum Machine Learning (QML), and Their Applications\n",
    "\n",
    "**Here, you have early access to our [New Classiq's documentation](https://nightly.docs.classiq.io/latest/)!**\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "Additional resources you should use are\n",
    "- The IDE of the classiq platform at [platform.classiq.io](platform.classiq.io)\n",
    "- The [community Slack of Classiq](https://short.classiq.io/join-slack) - Classiq's team will answer any question you have over there, including implementation questions\n",
    "- [Classiq's documentation](https://docs.classiq.io/latest/user-guide/platform/) with the dedicated [Python SDK explanations](https://docs.classiq.io/latest/user-guide/platform/qmod/python/functions/)\n",
    "\n",
    "Good luck!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setting The Scene"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Install the Classiq SDK package:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# !pip install -U classiq"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You need to authenticate your device in order to use Classiq's backend synthesis engine and IDE. \n",
    "**Make sure to register to the platform** at [platform.classiq.io](https://platform.classiq.io/) before you run the next cell:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "import classiq\n",
    "# classiq.authenticate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "from classiq import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A Warm Up"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### First Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Write a function that prepares the minus state $\\ket{-}=\\frac{1}{\\sqrt2}(\\ket{0}-\\ket{1})$, assuming it recives the qubit $\\ket{x}=\\ket{0}$ (hint): \n",
    "\n",
    "<details>\n",
    "<summary>\n",
    "HINT\n",
    "</summary>\n",
    "\n",
    "Use `H(x)`,`X(x)`\n",
    "</details>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def prepare_minus_state(x:QBit):\n",
    "    pass #TODO delete pass\n",
    "    #TODO prepare |-> function"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now we will test our code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x: Output[QBit]):\n",
    "    allocate(1,x) # Initialize the qubit x\n",
    "    pass #TODO delete pass\n",
    "    #TODO apply the function prepare_minus"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "quantum_model = create_model(main)\n",
    "quantum_program = synthesize(quantum_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/185d88cf-237a-4991-acb9-802f796c99ee?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "show(quantum_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Uniform Superposition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's continue warming up with creating a function that receives a quantum register and creates a uniform superposition for all qubits within this array. You should use the function `apply_to_all(gate_operand=, target=)`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def create_initial_state(reg: QArray[QBit]):\n",
    "    pass #TODO delete pass\n",
    "    #TODO use the function apply_to_all in order create a uniform superposition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Test your function by creating a new main function, synthesizing and viewing the circuit:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(): #TODO fill in the correct declaration here, what variables this model should output?\n",
    "    pass #TODO delete pass\n",
    "    #TODO allocate reg with a few qubits\n",
    "    create_initial_state(reg)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO uncomment the following line:\n",
    "# qprog = synthesize(create_model(main))\n",
    "\n",
    "#TODO show the quantum program"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Another implementation could utilize the `repeat(count=, iteration=)` function. The `repeat` function can be thought of as a \"classical for loop\". It could be handy in many situations, especially when combined with `if_` function, the \"classical if\" statement in Classiq. Together, they form 'Classical Control Flow'. \n",
    "\n",
    "Read more: [Classical Control Flow (repeat, if_)](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/classical-control-flow/?h=repea#__tabbed_1_2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/9ef28f93-59fe-467a-8b6d-efd7a0648fce?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "@qfunc\n",
    "def create_initial_state(q: QArray[QBit]) -> None:\n",
    "    repeat(q.len, lambda i: H(q[i]))\n",
    "\n",
    "@qfunc\n",
    "def main(reg: Output[QArray]): \n",
    "    allocate(4,reg)\n",
    "    create_initial_state(reg)\n",
    "\n",
    "qprog = synthesize(create_model(main))\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Guidelines for High-Level Functional Design with Classiq"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Some basic explanations about the high-level functional design with Classiq:**\n",
    "\n",
    "* There should always be a main (`def main(...)`) function - the model that captures your algorithm is described there\n",
    "\n",
    "* The model is always generated out of the main function \n",
    "\n",
    "* The model is sent to the synthesis engine (compiler) that return a quantum program which contains the quantum circuit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Some basic guidelines about the modeling language (QMOD):**\n",
    "\n",
    "1. Every function you use with the QMOD language should have the decorator `@qfunc` before it\n",
    "2. Every quantum variable should be declared, either as an argument of a function e.g. `def prepare_minus(x: QBit)` or as a local variable within the function itself with `x = QBit('x')`\n",
    "\n",
    "\n",
    "3. Some quantum variables need to be initialized with the `allocate` function. This is required in 2 cases:\n",
    "* A variable is an argument of a function with the declaration `Output` like `def main(x: Output[QNum])`\n",
    "* A variable that was declared within a function like `a = QNum('a')`\n",
    "\n",
    "4. For the `main` function, you will always use `Output` for all variables. The `output` indicates that these quantum variables are not initialized outside the scope of the function.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<details> \n",
    "<summary> Types of Initializations </summary>\n",
    "There are a few ways to initialize a quantum variable:\n",
    "\n",
    "1. With `allocate` or `allocate_num` \n",
    "2. With `prepare_int`, `prepare_state` or `prepare_amplitudes`\n",
    "3. As the result of a numeric operation `|=`\n",
    "4. With the `bind` operation (`->` in native)\n",
    "5. With any function that declares its quantum variable argument as `output`\n",
    "\n",
    "</details>\n",
    "\n",
    "<details> \n",
    "<summary> Types of Quantum Variables </summary>\n",
    "In Qmod there are 3 types of quantum variables:\n",
    "\n",
    "1. `QBit` (`qbit`)\n",
    "2. `QArray[QBit]` (`qbit[]`)\n",
    "3. `QNum` (`qnum`)\n",
    "\n",
    "(See also [Quantum Variables](https://nightly.docs.classiq.io/latest/classiq_101/classiq_concepts/design/quantum_variables_and_functions/))\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tutorial - State Preparation "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Prepare State"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we will see how we can easily make arbitrary state using Classiq's `prepare_state`.\n",
    "\n",
    "For example, let’s say we want to prepare the state $ \\ket{\\Phi^+}=\\frac{1}{\\sqrt{2}}(\\ket{00}+\\ket{11}) $"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/9e0cede6-2313-4bf2-a1c8-884b0ce731d7?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "@qfunc\n",
    "def main(x: Output[QArray[QBit]]):\n",
    "    prepare_state(probabilities=[0.5,0,0,0.5], bound=0.01, out=x)\n",
    "\n",
    "model = create_model(main)\n",
    "qprog = synthesize(model)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or using `prepare_bell_state` in order to prepare states with relative phase:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x:Output[QArray]):\n",
    "    prepare_bell_state(state_num=0, q=x) # phi-"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/b172cbea-d4a9-468e-9323-6159e9f0fb88?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "quantum_model = create_model(main)\n",
    "quantum_program = synthesize(quantum_model)\n",
    "show(quantum_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<details>\n",
    "<summary>\n",
    "NOTE\n",
    "</summary>\n",
    "\n",
    "\n",
    "| State Number | Bell State | \n",
    "|--------------|------------|\n",
    "| 0            | $ \\ket{\\phi^+}= \\frac{1}{\\sqrt{2}}[\\ket{00}+\\ket{11}] $  | \n",
    "| 1            | $ \\ket{\\phi^-}= \\frac{1}{\\sqrt{2}}[\\ket{00}-\\ket{11}]$  |\n",
    "| 2            | $ \\ket{\\psi^+}= \\frac{1}{\\sqrt{2}}[\\ket{01}+\\ket{10}]$  |\n",
    "| 3            | $ \\ket{\\psi^-}= \\frac{1}{\\sqrt{2}}[\\ket{01}-\\ket{10}]$  |\n",
    "| 4=0          | $ \\ket{\\phi^+}= ...$  |\n",
    "| ...          | ...        | ...   | \n",
    "\n",
    "\n",
    "</details>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now it's your turn to prepare the state $ \\ket{\\Psi^+}=\\frac{1}{\\sqrt{2}}(\\ket{01}+\\ket{10}) $"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x: Output[QArray[QBit]]):\n",
    "    pass #TODO delete pass\n",
    "    #TODO use the function prepare_state/ prepare_bell_state in order create a the desired state\n",
    "\n",
    "#TODO create model, synthesize it, and show it"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that we can use `prepare_state` to create much more complex states, for instance, we can create list of probabilities:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "jupyter": {
     "source_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "probabilities = [\n",
    "    0,\n",
    "    0.002,\n",
    "    0.004,\n",
    "    0.006,\n",
    "    0.0081,\n",
    "    0.0101,\n",
    "    0.0121,\n",
    "    0.0141,\n",
    "    0.0161,\n",
    "    0.0181,\n",
    "    0.0202,\n",
    "    0.0222,\n",
    "    0.0242,\n",
    "    0.0262,\n",
    "    0.0282,\n",
    "    0.0302,\n",
    "    0.0323,\n",
    "    0.0343,\n",
    "    0.0363,\n",
    "    0.0383,\n",
    "    0.0403,\n",
    "    0.0423,\n",
    "    0.0444,\n",
    "    0.0464,\n",
    "    0.0484,\n",
    "    0.0504,\n",
    "    0.0524,\n",
    "    0.0544,\n",
    "    0.0565,\n",
    "    0.0585,\n",
    "    0.0605,\n",
    "    0.0625,\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And then generate the state:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/42118092-b20e-4172-98c6-00943a067da4?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "@qfunc\n",
    "def main(x: Output[QArray[QBit]]):\n",
    "    prepare_state(probabilities=probabilities, bound=0.01, out=x)\n",
    "    \n",
    "model = create_model(main)\n",
    "qprog = synthesize(model)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Prepare Int"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We also have a `prepare_int` function, which allows us to register integers effortlessly. For example, $binary(9) = 1001$, or any other integer can be prepared in a single line of code:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/57617572-0d31-42e6-88c4-a54efb0d7b47?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "from classiq import *\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QNum]) -> None:\n",
    "    prepare_int(9, x)\n",
    "\n",
    "model = create_model(main)\n",
    "qprog = synthesize(model)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise: Parallel Addition "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this exercise, we will conclude state preparation and also get a teaser of the next part of the tutorial - Arithmetic Operations. \n",
    "\n",
    "Let's say that for some reason we used `prepare_int` to create the integer 7, and we want to perform addition operations with the integers 0, 4, and 7. We will do that using `prepare_state`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(res: Output[QNum]) -> None:\n",
    "    pass #TODO delete pass\n",
    "    x = QNum() #TODO complete the declarations of x,y\n",
    "    y = QNum()\n",
    "    \n",
    "    # TODO prepare the above-mentioned integer using prepare_int\n",
    "    prepare_state(probabilities=[], bound=0.01, out=y) # TODO complete the 'probabilities' list\n",
    "    \n",
    "    res|=x+y\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO uncomment the folllowing lines:\n",
    "\n",
    "# model = create_model(main)\n",
    "# qprog = synthesize(model)\n",
    "# show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tutorial - Arithmetic Operations with Classiq"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "One of the key advantages of Classiq is it's simplistic and powerful compiler for quantum arithmetic. Let's see an example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "num_qubits = 4\n",
    "fraction_digits = 0 \n",
    "is_signed = True\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QNum], y: Output[QNum]):\n",
    "    allocate_num(num_qubits=num_qubits, is_signed=is_signed, fraction_digits=fraction_digits, out=x)\n",
    "    hadamard_transform(x)\n",
    "    y|= x**2 + 1\n",
    "\n",
    "qmod = create_model(main)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `allocate_num` function initializes a quantum variable that represent numbers. By default, it is initialized to the $\\ket{0}$ state. Then the `hadmard_transform` creates a superposition of all possible states in the domain $[-2^3,2^3-1]$. Finally, the arithmetic operation creates the entangled superposition of states:\n",
    "$\\begin{equation}\n",
    "\\sum_{x =-2^3}^{2^3-1}\\ket{x}\\ket{x^2+1}.\n",
    "\\end{equation}$\n",
    "\n",
    "The `qmod` variable is a string that captures the algorithm we have just created in a JSON format. Now, what we want is to synthesize (compile) in order to receive a concrete quantum program that contains the quantum circuit implementation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Advanced Arithmetics"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's create a general linear function with Classiq: $y= ax+b$ where $a,b$ are classical integer parameters and $x,y$ is a quantum states representing integers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def linear_func(a:CInt,b: CInt, x:QNum, y: Output[QNum]):\n",
    "    y |= a*x+b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x:Output[QNum], y: Output[QNum]):\n",
    "\n",
    "    a = 2\n",
    "    b = 1\n",
    "    allocate_num(num_qubits=4,is_signed=False,fraction_digits=0,out=x)\n",
    "    hadamard_transform(x)\n",
    "    linear_func(a,b,x,y)\n",
    "\n",
    "qmod = create_model(main)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "qprog = synthesize(qmod)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's execute the circuit from directly from the SDK:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "job = execute(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And we can view the results in the IDE:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "job.open_in_ide()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Or to directly analyze it within the SDK:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'x': 8.0, 'y': 17.0}\n",
      "{'x': 5.0, 'y': 11.0}\n",
      "{'x': 13.0, 'y': 27.0}\n",
      "{'x': 7.0, 'y': 15.0}\n",
      "{'x': 14.0, 'y': 29.0}\n",
      "{'x': 6.0, 'y': 13.0}\n",
      "{'x': 2.0, 'y': 5.0}\n",
      "{'x': 15.0, 'y': 31.0}\n",
      "{'x': 1.0, 'y': 3.0}\n",
      "{'x': 12.0, 'y': 25.0}\n",
      "{'x': 10.0, 'y': 21.0}\n",
      "{'x': 9.0, 'y': 19.0}\n",
      "{'x': 11.0, 'y': 23.0}\n",
      "{'x': 0.0, 'y': 1.0}\n",
      "{'x': 4.0, 'y': 9.0}\n",
      "{'x': 3.0, 'y': 7.0}\n"
     ]
    }
   ],
   "source": [
    "results = job.result()\n",
    "parsed_counts = results[0].value.parsed_counts\n",
    "for sampled_state in parsed_counts: print(sampled_state.state)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Now it's your turn!** \n",
    "\n",
    "Implement the same linear function, but now $x$ is in the domain $[0,1)$ and is represented by 4 qubits. The parameters $a,b$ should be now `float` with the values of: $a=0.5, b=1.5$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [],
   "source": [
    "#TODO complete here"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tutorial - Two controlled Linear operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's say we want now to have two linear operations applied on the same quantum variable (register). But the arithmetic operation initialize a new quantum variable, so how can we do that? The answer is that we need to apply the operation to another variable and then XOR it to the variable we want. \n",
    "\n",
    "This can be useful if the linear operation we want to apply is controlled upon a control variable. Let's first define the functional building block:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def inplace_linear_attempt(a:CInt,b: CInt, x:QNum, y: QNum):\n",
    "    tmp = QNum('tmp')\n",
    "    linear_func(a,b,x,tmp)\n",
    "    inplace_xor(tmp,y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And checking our basic function implementation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/6e172347-e427-4622-84d0-01d46371a787?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "@qfunc\n",
    "def main(x: Output[QNum],y: Output[QNum]):\n",
    "    a = 1\n",
    "    b = 2\n",
    "\n",
    "    allocate_num(4,False,0,y)\n",
    "    allocate_num(4,False,0,x)\n",
    "    hadamard_transform(x)\n",
    "    inplace_linear_attempt(a,b,x,y)\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "OK, cool. So now we want to add a control qubit that controlled on the state $\\ket{0}$ implements the linear function $\\ket{x}\\rightarrow\\ket{x}\\ket{x+2}$ and controlled on the state $\\ket{1}$ implements the linear function $\\ket{x}\\rightarrow\\ket{x}\\ket{2x+1}$:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "scrolled": true
   },
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def control_logic(a: CArray[CInt], b: CArray[CInt], controller:QNum, x: QNum, y: QNum):\n",
    "    \n",
    "    repeat( count=a.len,         \n",
    "            iteration=lambda i: control(controller==i, lambda: inplace_linear_attempt(a[i],b[i],x,y)))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(controller: Output[QNum], x: Output[QNum],y: Output[QNum]):\n",
    "\n",
    "    # Linear polynom parameters\n",
    "    a = [1,2]\n",
    "    b = [2,1]\n",
    "\n",
    "    # Initializing x to a superposition in the domain [0,2^4-1]\n",
    "    allocate_num(4,False,0,x)\n",
    "    hadamard_transform(x)\n",
    "    \n",
    "    #Initialize y\n",
    "    allocate_num(4,False,0,y)\n",
    "\n",
    "    # Setting the controller in a superposition\n",
    "    allocate_num(1,False,0,controller)\n",
    "    H(controller)\n",
    "\n",
    "    # Implementing the control logic\n",
    "    control_logic(a,b,controller,x,y)\n",
    "\n",
    "    \n",
    "qmod = create_model(main)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/8c213254-0a37-4a2c-94b9-c43e52da760e?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "By executing we can actually see we get what we want:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'controller': 0.0, 'x': 15.0, 'y': 1.0}\n",
      "{'controller': 0.0, 'x': 6.0, 'y': 8.0}\n",
      "{'controller': 1.0, 'x': 4.0, 'y': 9.0}\n",
      "{'controller': 0.0, 'x': 8.0, 'y': 10.0}\n",
      "{'controller': 0.0, 'x': 11.0, 'y': 13.0}\n",
      "{'controller': 0.0, 'x': 7.0, 'y': 9.0}\n",
      "{'controller': 0.0, 'x': 13.0, 'y': 15.0}\n",
      "{'controller': 1.0, 'x': 0.0, 'y': 1.0}\n",
      "{'controller': 1.0, 'x': 7.0, 'y': 15.0}\n",
      "{'controller': 1.0, 'x': 12.0, 'y': 9.0}\n",
      "{'controller': 0.0, 'x': 0.0, 'y': 2.0}\n",
      "{'controller': 1.0, 'x': 10.0, 'y': 5.0}\n",
      "{'controller': 1.0, 'x': 1.0, 'y': 3.0}\n",
      "{'controller': 0.0, 'x': 9.0, 'y': 11.0}\n",
      "{'controller': 1.0, 'x': 5.0, 'y': 11.0}\n",
      "{'controller': 1.0, 'x': 6.0, 'y': 13.0}\n",
      "{'controller': 1.0, 'x': 9.0, 'y': 3.0}\n",
      "{'controller': 0.0, 'x': 5.0, 'y': 7.0}\n",
      "{'controller': 0.0, 'x': 2.0, 'y': 4.0}\n",
      "{'controller': 1.0, 'x': 11.0, 'y': 7.0}\n",
      "{'controller': 0.0, 'x': 4.0, 'y': 6.0}\n",
      "{'controller': 0.0, 'x': 3.0, 'y': 5.0}\n",
      "{'controller': 1.0, 'x': 2.0, 'y': 5.0}\n",
      "{'controller': 0.0, 'x': 1.0, 'y': 3.0}\n",
      "{'controller': 0.0, 'x': 14.0, 'y': 0.0}\n",
      "{'controller': 0.0, 'x': 12.0, 'y': 14.0}\n",
      "{'controller': 1.0, 'x': 13.0, 'y': 11.0}\n",
      "{'controller': 1.0, 'x': 3.0, 'y': 7.0}\n",
      "{'controller': 0.0, 'x': 10.0, 'y': 12.0}\n",
      "{'controller': 1.0, 'x': 8.0, 'y': 1.0}\n",
      "{'controller': 1.0, 'x': 14.0, 'y': 13.0}\n",
      "{'controller': 1.0, 'x': 15.0, 'y': 15.0}\n"
     ]
    }
   ],
   "source": [
    "def print_parsed_counts(job):\n",
    "    results = job.result()\n",
    "    parsed_counts = results[0].value.parsed_counts\n",
    "    for parsed_state in parsed_counts: print(parsed_state.state)\n",
    "\n",
    "job = execute(qprog)\n",
    "print_parsed_counts(job)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Of course there is the issue of rounding and overflow - when one tries to represent $2*15+1=31$ with $4$ binary digits that's not possible (because the domain $[0,31]$ of integers is represented by at least 5 bits). See our [documentation](https://docs.classiq.io/latest/user-guide/platform/qmod/python/quantum-expressions/#inplace-arithmetic-operators) for further explanations.\n",
    "\n",
    "Let's try to use Classiq and optimize the circuit for minimal circuit width:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Synthesized circuit width: 20, depth: 688\n"
     ]
    }
   ],
   "source": [
    "def print_depth_width(quantum_program):\n",
    "    generated_circuit = QuantumProgram.parse_raw(quantum_program)\n",
    "    print(f\"Synthesized circuit width: {generated_circuit.data.width}, depth: {generated_circuit.transpiled_circuit.depth}\")\n",
    " \n",
    "qmod = set_constraints(qmod,Constraints(optimization_parameter='width'))\n",
    "qprog = synthesize(qmod)\n",
    "print_depth_width(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And when optimizing for depth:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Synthesized circuit width: 32, depth: 398\n"
     ]
    }
   ],
   "source": [
    "qmod = set_constraints(qmod,Constraints(optimization_parameter='depth'))\n",
    "qprog = synthesize(qmod)\n",
    "print_depth_width(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Firstly, we can see here a clear demonstration of the power of high-level functional design!** The same algorithm with the same functionality was optimized once for depth and once for width and the result is 2 different circuits with different characteristics that implement the same functionality.\n",
    "\n",
    "Secondly, is this the best we can do? Obviously the Classiq synthesis engine is optimizing for us the algorithm quite good. But, can we change something with our functionality, with our algorithm to get more efficient circuits? \n",
    "\n",
    "If we go back to out `inplace_linear_attempt` function, we can see that we initialize a `tmp` variable that requires more qubits and is not used. For such scenarios we have the `within_apply`. This logic implements sort of $UVU^{\\dagger}$ and when temporary variables are outputs of $U$ and used only by $V$ they are uncomputed by $U^{\\dagger}$. Let's see for our example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def inplace_linear_func(a:CInt,b: CInt, x:QNum, y: QNum):\n",
    "    tmp = QNum('tmp')\n",
    "    within_apply(compute= lambda: linear_func(a,b,x,tmp),\n",
    "                action= lambda: inplace_xor(tmp,y))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "With the new `control_logic`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def control_logic_2(a: CArray[CInt], b: CArray[CInt], controller:QNum, x: QNum, y: QNum):\n",
    "    \n",
    "    repeat( count=a.len,         \n",
    "            iteration=lambda i: control(controller==i, lambda: inplace_linear_func(a[i],b[i],x,y)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And when we put all together now:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(controller: Output[QNum], x: Output[QNum],y: Output[QNum]):\n",
    "\n",
    "    # Linear polynom parameters\n",
    "    a = [1,2]\n",
    "    b = [2,1]\n",
    "\n",
    "    # Initializing x to a superposition in the domain [0,2^4-1]\n",
    "    allocate_num(4,False,0,x)\n",
    "    hadamard_transform(x)\n",
    "    \n",
    "    #Initialize y\n",
    "    allocate_num(4,False,0,y)\n",
    "\n",
    "    # Setting the controller in a superposition\n",
    "    allocate_num(1,False,0,controller)\n",
    "    H(controller)\n",
    "\n",
    "    # Implementing the control logic\n",
    "    control_logic_2(a,b,controller,x,y)\n",
    "\n",
    "    \n",
    "qmod = create_model(main)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "qprog = synthesize(qmod)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Opening: https://platform.classiq.io/circuit/46696929-79f8-43c0-87e9-fdbebd48156f?version=0.40.0\n"
     ]
    }
   ],
   "source": [
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And now when we optimize for width:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qmod = set_constraints(qmod,Constraints(optimization_parameter='width'))\n",
    "qprog = synthesize(qmod)\n",
    "print_depth_width(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And for depth:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "qmod = set_constraints(qmod,Constraints(optimization_parameter='depth'))\n",
    "qprog = synthesize(qmod)\n",
    "print_depth_width(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So using the `within_apply` logic enabled us to reduce the optimal circuit implementation in terms of width from $20$ to $16$ and the optimal circuit depth from $398$ to $203$! "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "jp-MarkdownHeadingCollapsed": true
   },
   "source": [
    "## Cheat Sheet"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Initalizations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "allocate(\n",
    "    num_qubits: CInt,\n",
    "    out: Output[QArray[QBit, Literal[\"num_qubits\"]]])\n",
    "    '''\n",
    "    x = QArray('x')\n",
    "    allocate(4,x)\n",
    "    '''\n",
    "\n",
    "allocate_num(\n",
    "    num_qubits: CInt,\n",
    "    is_signed: QParam[bool],\n",
    "    fraction_digits: CInt,\n",
    "    out: Output[QNum])\n",
    "'''\n",
    "x = QNum('x')\n",
    "allocate_num(4,False,4,x)\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "repeat(\n",
    "    count: CInt,\n",
    "    iteration: QCallable[CInt],\n",
    ")\n",
    "'''\n",
    "x = QArray\n",
    "allocate(4,x)\n",
    "repeat(x.len,lambda index: H(x))\n",
    "'''\n",
    "\n",
    "control(\n",
    "    operand: QCallable,\n",
    "    ctrl: QArray[QBit],\n",
    ") \n",
    "'''\n",
    "x = QArray('x')\n",
    "y = QArray('y')\n",
    "x = allocate(4,x)\n",
    "y = allocate(4,y)\n",
    "repeat(x.len,lambda i: control(lambda: X(y[i]),x[i]))\n",
    "'''"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Solutions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### First Example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def prepare_minus_state(x:QBit):\n",
    "    X(x)\n",
    "    H(x)\n",
    "    \n",
    "@qfunc\n",
    "def main(x: Output[QBit]):\n",
    "    allocate(1,x) # Initalize the qubit x\n",
    "    prepare_minus_state(x)\n",
    "\n",
    "quantum_model = create_model(main)\n",
    "quantum_program = synthesize(quantum_model)\n",
    "show(quantum_program)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Uniform Superposition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def create_initial_state(reg: QArray[QBit]):\n",
    "    apply_to_all(H,reg)\n",
    "\n",
    "@qfunc\n",
    "def main(reg: Output[QArray]): #TODO fill int the correct declaration here, what variables this model shoul output?\n",
    "    allocate(4,reg)\n",
    "    create_initial_state(reg)\n",
    "\n",
    "qprog = synthesize(create_model(main))\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Prepare Bell State"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x:Output[QArray]):\n",
    "    prepare_bell_state(state_num=2, q=x) # psi+\n",
    "\n",
    "# Or:\n",
    "\n",
    "@qfunc\n",
    "def main(x: Output[QArray[QBit]]):\n",
    "    prepare_state(probabilities=[0,0.5,0.5,0], bound=0.01, out=x)\n",
    "\n",
    "model = create_model(main)\n",
    "qprog = synthesize(model)\n",
    "show(qprog)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Parallel Addition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(res: Output[QNum]) -> None:\n",
    "    x = QNum(\"x\")\n",
    "    prepare_int(7,x)\n",
    "    \n",
    "    y = QNum(\"y\")\n",
    "    prepare_state(probabilities=[1/3,0,0,0,1/3,0,0,1/3], bound=0.01, out=y)\n",
    "    res|=x+y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Advanced Arithmetics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@qfunc\n",
    "def main(x:Output[QNum], y: Output[QNum]):\n",
    "\n",
    "    a = 0.5\n",
    "    b = 1.5\n",
    "    allocate_num(num_qubits=4,is_signed=False,fraction_digits=4,out=x)\n",
    "    hadamard_transform(x)\n",
    "    linear_func(a,b,x,y)\n",
    "\n",
    "qmod = create_model(main)\n",
    "qprog = synthesize(qmod)\n",
    "show(qprog)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  },
  "vscode": {
   "interpreter": {
    "hash": "529b62266d4f537a408698cf820854c65fe877011c7661f0f70aa11c4383fddc"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
